import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;


public class InvaderSaver {

	private static int gameWidth;
	private static int gameHeight;
	static final int STEPS_PER_SECOND = 60; // should always be a multiple of 30
	static final int GAMESPEED_MODIFIER = STEPS_PER_SECOND/30; // used to change speeds/rates of fire/etc 
										 // throughout the game if STEPS_PER_SECOND changes 
	static final int MILLISECONDS_PER_SECOND = 1000;
	static final int EXIT_SUCCESS = 0;
	Dimension screenSize;
	
	// Game Objects
	private Defender defender;
	private Wave wave;
	private ProjectileControl projectiles;
	private ArrayList<Base> bases;
	
	// game input control variables
	static boolean UP = false;
	static boolean LEFT = false;
	static boolean RIGHT = false;
	
	// game timer
	static Timer clock;
	
	// double-buffering tools
	BufferedImage buffer;
	
	// stores the mode of control after being input
	enum TargetingAI{HUMAN, RANDOM, LEFTMOST, RIGHTMOST, LOWEST, SQUARE, HYBRID};
	enum AimingAI{HUMAN, UNDER, ANTICIPATE};
	enum EvadingAI{HUMAN, SIMPLE, FORESIGHT};
	private static TargetingAI targetAI;
	private static AimingAI aimAI;
	private static EvadingAI evadeAI;
	
	// Game options
	boolean gameBounces;
	boolean projectilesCollide;
	
	public static void main(String[] args){
		new InvaderSaver();
	}
	
	public InvaderSaver(){
		screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		gameWidth = 640;
		gameHeight = 480;
		targetAI = TargetingAI.SQUARE; //TODO: use input to determine this.
		aimAI = AimingAI.ANTICIPATE;
		evadeAI = EvadingAI.FORESIGHT;
		gameBounces = true;			 //TODO: this, too
		projectilesCollide = true;		 //TODO: this, too
		new SpriteLib(); // fetch game sprites
		
		defender = new Defender(targetAI, aimAI, evadeAI);
		wave = new Wave();
		projectiles = new ProjectileControl(defender, projectilesCollide);
		bases = new ArrayList<Base>();
		// create all of the starting Bases
		createBase(20+30, 380);
		createBase(174+30, 380);
		createBase(328+30, 380);
		createBase(482+30, 380);
		
		new InvaderFrame();
	}
	
	/**
	 * creates 12 Bases to make a defensive structure.
	 * @param x
	 * @param y
	 */
	private void createBase(int x, int y){
		int col = SpriteLib.BASE_COMPONENTS.image_width;
		int row = SpriteLib.BASE_COMPONENTS.image_height;
		bases.add(new Base(x, y, 4));
		bases.add(new Base(x+col, y, 0));
		bases.add(new Base(x+col*2, y, 0));
		bases.add(new Base(x+col*3, y, 8));
		bases.add(new Base(x, y+row, 0));
		bases.add(new Base(x+col, y+row, 12));
		bases.add(new Base(x+col*2, y+row, 16));
		bases.add(new Base(x+col*3, y+row, 0));
		bases.add(new Base(x, y+row*2, 0));
		bases.add(new Base(x+col*3, y+row*2, 0));
	}
	
	static public int getGameWidth(){
		return gameWidth;
	}
	static public int getGameHeight(){
		//return 480; // use this in conjunction with the Test class
		return gameHeight;
	}
	
	/**
	 * The window for the screensaver
	 *
	 */
	private class InvaderFrame extends JFrame
	{
//		private static final long serialVersionUID = 1L; // prevents a warning
		InvaderPanel IP;
		
		// vars for controlling the game's motion
		double panelXspeed;
		double panelYspeed;
		double panelX;
		double panelY;
		
		public InvaderFrame(){
			setSize(screenSize.width, screenSize.height);
//			setSize(640, 480);
//			setLocation(400, 200);
			setUndecorated(true);
			
			IP = new InvaderPanel();
			add(IP);
			panelX = 0;
			panelY = 0;
			panelXspeed = 4;
			panelYspeed = 4;
			
			setVisible(true);
			
			clock = new Timer(MILLISECONDS_PER_SECOND/STEPS_PER_SECOND, new ActionListener(){
				@Override
				public void actionPerformed(ActionEvent arg0) {
					/* ********************************************************************** *
					 * HEREIN LIES THE GAME CODE, EXECUTED EVERY TIMESTEP
					 * 
					 * 
					 * ********************************************************************** */

					////////////////First, make the whole game bounce around
					if(gameBounces){
						if(panelX >= screenSize.width - IP.getWidth() && panelXspeed > 0){
							panelXspeed = Math.floor((Math.random()*5 + 1) * -1)/InvaderSaver.GAMESPEED_MODIFIER;
						}
						if(panelX <= 0 && panelXspeed < 0){
							panelXspeed = Math.floor((Math.random()*5 + 1))/InvaderSaver.GAMESPEED_MODIFIER;
						}
						if(panelY >= screenSize.height - IP.getHeight() && panelYspeed > 0){
							panelYspeed = Math.floor((Math.random()*5 + 1) * -1)/InvaderSaver.GAMESPEED_MODIFIER;
						}
						if(panelY <= 0 && panelYspeed < 0){
							panelYspeed = Math.floor((Math.random()*5 + 1))/InvaderSaver.GAMESPEED_MODIFIER;
						}
						
						panelX += panelXspeed;
						panelY += panelYspeed;
						IP.setLocation((int)panelX, (int)panelY);
					}
					//////////////// teeheehee
					
					// update objects
					defender.update(wave, bases);
					wave.update();
					projectiles.update(wave, bases);
					
					// garbage collect any destroyed sections of Bases
					ArrayList<Base> baseCopies = new ArrayList<Base>();
					baseCopies.addAll(bases);
					bases.clear();
					
					for(Base base:baseCopies){
						if(base.structuralIntegrity() > 0){
							bases.add(base);
						}
					}
					
					// If the Defender is dead...
					if(defender.resetMe()){
						if(HUD.readLives() > 0){ // still have lives left
							HUD.modifyLives(-1);
							defender.reset();
						}
						else{
							HUD.gameOver();  // G A M E   O V E R
							if(HUD.resetMe()){
								System.out.println("Final Score: " + HUD.readScore());
								HUD.resetGame();		// reset control variables (lives, points etc.)
								projectiles.clear();	// remove any remaining projectiles
								wave.setWaveNum(0);		// reset Wave start position
								
								// rebuild the bases
								bases.clear();
								createBase(20+30, 380);
								createBase(174+30, 380);
								createBase(328+30, 380);
								createBase(482+30, 380);

								wave.startNewWave();	// Begin the siege anew!
							}
						}
					}
					
					// If All the Invaders are dead...
					if(wave.getInvaders().isEmpty()){
						wave.startNewWave();
					}
					
					// draw
					repaint();
				}});
			
			// Listener to close the application upon any keystroke
			addKeyListener(new KeyListener(){
				@Override
				public void keyPressed(KeyEvent arg0) {
					if((targetAI != TargetingAI.HUMAN && aimAI != AimingAI.HUMAN && evadeAI != EvadingAI.HUMAN) || arg0.getKeyCode() == KeyEvent.VK_ESCAPE){
						System.out.println("Score: " + HUD.readScore());
						System.exit(EXIT_SUCCESS);
					}
					
					int keyCode = arg0.getKeyCode();
					switch(keyCode){
						case KeyEvent.VK_UP:	// UP
						case KeyEvent.VK_W:
							UP = true;
							break;
						case KeyEvent.VK_LEFT:	// LEFT
						case KeyEvent.VK_A:
							LEFT = true;
							break;
						case KeyEvent.VK_RIGHT:	// RIGHT
						case KeyEvent.VK_D:
							RIGHT = true;
							break;
					}
				}
				@Override
				public void keyReleased(KeyEvent arg0) {	
					if((targetAI != TargetingAI.HUMAN && aimAI != AimingAI.HUMAN && evadeAI != EvadingAI.HUMAN) || arg0.getKeyCode() == KeyEvent.VK_ESCAPE){
						System.exit(EXIT_SUCCESS);
					}
					
					int keyCode = arg0.getKeyCode();
					switch(keyCode){
						case KeyEvent.VK_UP:	// UP
						case KeyEvent.VK_W:
							UP = false;
							break;
						case KeyEvent.VK_LEFT:	// LEFT
						case KeyEvent.VK_A:
							LEFT = false;
							break;
						case KeyEvent.VK_RIGHT:	// RIGHT
						case KeyEvent.VK_D:
							RIGHT = false;
							break;
					}
				}
				@Override
				public void keyTyped(KeyEvent arg0) {	
					if((targetAI != TargetingAI.HUMAN && aimAI != AimingAI.HUMAN && evadeAI != EvadingAI.HUMAN) || arg0.getKeyCode() == KeyEvent.VK_ESCAPE){
						System.exit(EXIT_SUCCESS);
					}
				}
			});
			
			clock.start();
		}
		/**
		 * A frame on which to draw
		 */
		private class InvaderPanel extends JPanel
		{
			private Boolean ignoreFirstMouseMove = true; 
//			private static final long serialVersionUID = 1L; // prevents a warning
			public InvaderPanel(){
				
				addMouseMotionListener(new MouseMotionListener(){

					@Override
					public void mouseDragged(MouseEvent e) {
//						if(controlMode != ControlMode.HUMAN){
						if(targetAI != TargetingAI.HUMAN && aimAI != AimingAI.HUMAN && evadeAI != EvadingAI.HUMAN){
							System.exit(EXIT_SUCCESS);
						}
					}

					@Override
					public void mouseMoved(MouseEvent e) {
						if(ignoreFirstMouseMove){
							ignoreFirstMouseMove = false;
							// Okay, so this may be bad code or something shameful, but I'm also
							// going to use this place to set the size of this thing, since
							// for some reason it won't work in the constructor... go figure.
							setSize(gameWidth, gameHeight);
							//setLocation(screenSize.width/2 - gameWidth/2, screenSize.height/2 - gameHeight/2);
							setLocation(0,0);
						}
						else{
							if(targetAI != TargetingAI.HUMAN && aimAI != AimingAI.HUMAN && evadeAI != EvadingAI.HUMAN){
								System.exit(EXIT_SUCCESS);
							}
						}
					}
				});
				
				addMouseListener(new MouseAdapter(){
					public void mousePressed(MouseEvent e){
						if(targetAI != TargetingAI.HUMAN && aimAI != AimingAI.HUMAN && evadeAI != EvadingAI.HUMAN){
							System.exit(EXIT_SUCCESS);
						}
					}
				});
			} //InvaderPanel()

			public void paintComponent(Graphics g){
				g.setColor(Color.BLACK);
				g.fillRect(0, 0, gameWidth, gameHeight);
				for(Base base: bases){
					base.draw(g, this);
				}
				wave.draw(g, this);
				projectiles.draw(g, this);
				defender.draw(g, this);
				HUD.draw(g, this);
			}
		} // InvaderPanel
	} // InvaderFrame
	
	public void say(String words){
		System.out.println(words);
	}
} // InvaderSaver
